// synch.cc 
//	Routines for synchronizing threads.  Three kinds of
//	synchronization routines are defined here: semaphores, locks 
//   	and condition variables (the implementation of the last two
//	are left to the reader).
//
// Any implementation of a synchronization routine needs some
// primitive atomic operation.  We assume Nachos is running on
// a uniprocessor, and thus atomicity can be provided by
// turning off interrupts.  While interrupts are disabled, no
// context switch can occur, and thus the current thread is guaranteed
// to hold the CPU throughout, until interrupts are reenabled.
//
// Because some of these routines might be called with interrupts
// already disabled (Semaphore::V for one), instead of turning
// on interrupts at the end of the atomic operation, we always simply
// re-set the interrupt state back to its original value (whether
// that be disabled or enabled).
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved.  See copyright.h for copyright notice and limitation 
// of liability and disclaimer of warranty provisions.

#include "copyright.h"
#include "synch.h"
#include "system.h"

#ifdef NETWORK
#include "post.h"
#endif

//----------------------------------------------------------------------
// Semaphore::Semaphore
// 	Initialize a semaphore, so that it can be used for synchronization.
//
//	"debugName" is an arbitrary name, useful for debugging.
//	"initialValue" is the initial value of the semaphore.
//----------------------------------------------------------------------

Semaphore::Semaphore(char* debugName, int initialValue)
{
    name = debugName;
    value = initialValue;
    queue = new List;
}

//----------------------------------------------------------------------
// Semaphore::Semaphore
// 	De-allocate semaphore, when no longer needed.  Assume no one
//	is still waiting on the semaphore!
//----------------------------------------------------------------------

Semaphore::~Semaphore()
{
    delete queue;
}

//----------------------------------------------------------------------
// Semaphore::P
// 	Wait until semaphore value > 0, then decrement.  Checking the
//	value and decrementing must be done atomically, so we
//	need to disable interrupts before checking the value.
//
//	Note that Thread::Sleep assumes that interrupts are disabled
//	when it is called.
//----------------------------------------------------------------------

void
Semaphore::P()
{
    IntStatus oldLevel = interrupt->SetLevel(IntOff);	// disable interrupts
    
    while (value == 0) { 			// semaphore not available
	queue->Append((void *)currentThread);	// so go to sleep
	currentThread->Sleep();
    } 
    value--; 					// semaphore available, 
						// consume its value
    
    (void) interrupt->SetLevel(oldLevel);	// re-enable interrupts
}

//----------------------------------------------------------------------
// Semaphore::V
// 	Increment semaphore value, waking up a waiter if necessary.
//	As with P(), this operation must be atomic, so we need to disable
//	interrupts.  Scheduler::ReadyToRun() assumes that threads
//	are disabled when it is called.
//----------------------------------------------------------------------

void
Semaphore::V()
{
    Thread *thread;
    IntStatus oldLevel = interrupt->SetLevel(IntOff);

    thread = (Thread *)queue->Remove();
    if (thread != NULL)	   // make thread ready, consuming the V immediately
	scheduler->ReadyToRun(thread);
    value++;
    (void) interrupt->SetLevel(oldLevel);
}

// Dummy functions -- so we can compile our later assignments 
// Note -- without a correct implementation of Condition::Wait(), 
// the test case in the network assignment won't work!
//Lock constructor, intializes name for debugging and intializes the "wait queue"
Lock::Lock(char* debugName) 
{
	name = debugName;
	lockOwner = NULL;
	busyOrFree = 0; 	//initialized to 0 = FREE.
	waitQueue = new List;
}
//Lock Destructor, just deletes the queue for now
Lock::~Lock() 
{
	delete waitQueue;
}

void Lock::Acquire() 
{
	IntStatus oldLevel = interrupt->SetLevel(IntOff);	// disable interrupts
	//printf("~~~DEBUG: Thread {%s} attempting to ACQUIRE lock {%s}\n",currentThread->getName(),getName());
	if(currentThread == lockOwner)					//If I'm the lock owner
	{
		//printf("~~~DEBUG: Thread {%s} is already lock owner of lock {%s}\n",currentThread->getName(),getName());
		(void) interrupt->SetLevel(oldLevel);	//restores interrupt
		return;					
	}
	if(busyOrFree == 0)				//If the lock is FREE
	{
		//printf("~~~DEBUG: Thread {%s} HAS ACQUIRED lock {%s}\n",currentThread->getName(),getName());
		busyOrFree = 1; 			//makes the lock BUSY
		lockOwner = currentThread;	//make myself the lock owner
	}
	else							//If lock is BUSY and I'm not the lock owner
	{
		//printf("~~~DEBUG: Thread {%s} will be added to wait queue for lock {%s}, current owner is thread {%s}\n",currentThread->getName(),getName(),lockOwner->getName());
		waitQueue->Append((void *)currentThread);	//add myself to the wait queue
		currentThread->Sleep();						//puts myself to sleep
	}
	(void) interrupt->SetLevel(oldLevel);	//restores interrupt
	return;	
}
void Lock::Release() 
{
	IntStatus oldLevel = interrupt->SetLevel(IntOff);	// disable interrupts
	if(currentThread != lockOwner)
	{
		//printf("~~~DEBUG: ERROR 7: Thread {%s} is not the owner of %s! CANNOT release the lock\n",currentThread->getName(),name);
		(void) interrupt->SetLevel(oldLevel);	//restores interrupt
		return;		
	}
	if(!waitQueue->IsEmpty())	//If the waitQueue is not empty
	{
		Thread *thread;
		thread = (Thread *)waitQueue->Remove();	//removes a thread from the wait queue
		if(thread != NULL)
		{
			//printf("~~~DEBUG: Thread {%s} RELEASING lock {%s}\n",currentThread->getName(),getName());
			//printf("~~~DEBUG: Thread {%s} HAS ACQUIRED lock {%s} from {%s}\n",thread->getName(),getName(),currentThread->getName());
			lockOwner = thread;	//make this thread the new owner;
			scheduler->ReadyToRun(thread);	//puts the thread in the ready queue
		}
	}
	else
	{
		//printf("~~~DEBUG: Thread {%s} RELEASING lock {%s}\n",currentThread->getName(),getName());
		busyOrFree = 0; 	//Changes the state of the lock to FREE
		lockOwner = NULL;	//Clear the owner of the lock
	}
	(void) interrupt->SetLevel(oldLevel);	//restores interrupt
	return;	
}

bool Lock::isBusy()
{
	IntStatus oldLevel = interrupt->SetLevel(IntOff);
	if(busyOrFree)
	{
		//BUSY
		interrupt->SetLevel(oldLevel);
		return 1;
	}
	else
	{
		//FREE
		interrupt->SetLevel(oldLevel);
		return 0;
	}
}

//Condition variable constructor initializes waitingLock to null;
Condition::Condition(char* debugName) 
{
	name = debugName;
	waitingLock = NULL;
	waitQueue = new List;
}
//Condition variable destructor
Condition::~Condition() 
{
	delete waitQueue;
}

void Condition::Wait(Lock* conditionLock) 
{
	IntStatus oldLevel = interrupt->SetLevel(IntOff);	// disable interrupts
	if(conditionLock == NULL) 
	{
		//printf("~~~DEBUG: ERROR 5: The Lock argument is NULL, Thread {%s}, CV %s (conditionLock: %s, waitingLock: %s)\n",currentThread->getName(),getName(),conditionLock->getName(),waitingLock->getName());
		(void) interrupt->SetLevel(oldLevel);	//restores interrupt
		return;	
	}
	if(waitingLock==NULL) //If the lock has not been set yet
	{
		waitingLock = conditionLock;
	}
	if(conditionLock!= waitingLock) //If the current lock is not the same as the argument lock
	{
		//printf("~~~DEBUG: ERROR 6: Thread {%s} Cannot release a DIFFERENT lock on CV %s (conditionLock: %s, waitingLock: %s)\n",currentThread->getName(),getName(),conditionLock->getName(),waitingLock->getName());
		(void) interrupt->SetLevel(oldLevel);	//restores interrupt
		return;	
	}
	//If we get here, it's okay to wait.
	//printf("~~~DEBUG: Thread {%s} going to WAIT on CV {%s}, lock {%s}\n",currentThread->getName(),getName(),conditionLock->getName());
	conditionLock->Release();
	waitQueue->Append((void *)currentThread);	//add myself to the wait queue
	currentThread->Sleep();						//puts myself to sleep
	//*******AT THIS POINT WE'RE WAITING TO BE WOKEN UP*************//
	//printf("~~~DEBUG: Thread {%s} WAKING UP from CV {%s}, lock {%s}\n",currentThread->getName(),getName(),conditionLock->getName());
	conditionLock->Acquire();
	(void) interrupt->SetLevel(oldLevel);	//restores interrupt
	return;
}
void Condition::Signal(Lock* conditionLock) 
{
	
	IntStatus oldLevel = interrupt->SetLevel(IntOff);	// disable interrupts
	if(waitQueue == NULL || waitQueue->IsEmpty()) // if no ones waiting, do nothing cause we don't need to signal anyone
	{
		//printf("~~~DEBUG: WARNING 1: Thread {%s} SIGNALING on CV {%s}, lock {%s}, but waitQueue is NULL or empty\n",currentThread->getName(), getName(),conditionLock->getName());
		(void) interrupt->SetLevel(oldLevel);	//restores interrupt
		return;
	}
	if(waitingLock!=conditionLock)//Cannot signal, locks dont match
	{
		//printf("~~~DEBUG: ERROR 2: Thread {%s} SIGNALING on CV {%s}, lock {%s} but lock argument and waitingLock do not agree\n",currentThread->getName(),getName(),conditionLock->getName());
		(void) interrupt->SetLevel(oldLevel);	//restores interrupt
		return;
	}
	Thread *thread;
	thread = (Thread *)waitQueue->Remove();	//removes a thread from the wait queue
	if(thread != NULL)
	{
		//printf("~~~DEBUG: Thread {%s} SIGNALING thread {%s} on CV {%s}, lock {%s}\n",currentThread->getName(),thread->getName(), getName(),conditionLock->getName());
		scheduler->ReadyToRun(thread);	//puts the thread in the ready queue
	}
	if(waitQueue->IsEmpty()) // if no ones waiting
	{
		////printf("~~~DEBUG: WARNING 2: Thread {%s} SIGNALING on CV {%s}, lock {%s}, but waitQueue is empty\n",currentThread->getName(), getName(),conditionLock->getName());
		waitingLock = NULL;
	}
	(void) interrupt->SetLevel(oldLevel);	//restores interrupt
	return;
}
void Condition::Broadcast(Lock* conditionLock) 
{
	IntStatus oldLevel = interrupt->SetLevel(IntOff);	// disable interrupts
	if(waitQueue == NULL || waitQueue->IsEmpty()) // if no ones waiting, restore interrupts because we don't need to broadcast anything
	{
		//printf("~~~DEBUG: WARNING 2: Thread {%s} BROADCASTING on CV {%s}, lock {%s}, but waitQueue is NULL or empty\n",currentThread->getName(), getName(),conditionLock->getName());
		(void) interrupt->SetLevel(oldLevel);	//restores interrupt
		return;
	}
	if(waitingLock!=conditionLock)//Cannot signal, locks dont match
	{
		//printf("~~~DEBUG: ERROR 4: Lock argument and waitingLock do not agree\n");
		(void) interrupt->SetLevel(oldLevel);	//restores interrupt
		return;
	}
	while(!waitQueue->IsEmpty())
	{
		Thread *thread;
		thread = (Thread *)waitQueue->Remove();
		if(thread != NULL)
		{
			//printf("~~~DEBUG: Thread {%s} BROADCASTING, waking up thread {%s} on CV {%s}, lock {%s}\n",currentThread->getName(),thread->getName(), getName(),conditionLock->getName());
			scheduler->ReadyToRun(thread);	//puts the thread in the ready queue
		}
	}
	if(waitQueue->IsEmpty()) //Makes sure the waitQueue is empty
	{
		waitingLock = NULL;
	}
	(void) interrupt->SetLevel(oldLevel);	//restores interrupt
	return;
}


#ifdef NETWORK
ServerLock::ServerLock(char* debugName)
{
	strcpy(name, debugName);
	busyOrFree = 0;  //Set lock state to free.
	machineID = -1;	//initialize machineID to -1
	mailboxNumber = -1; //initialize mailbox number to -1
	waitQueue = new List;
}

ServerLock::~ServerLock()
{
	delete[] name;
	delete waitQueue;
}

int ServerLock::Acquire(int machID, int mailNum)
{
	IntStatus oldLevel = interrupt->SetLevel(IntOff);
	if (machineID == machID && mailboxNumber == mailNum)
	{
		DEBUG('n',"ERROR: Lock already acquired by machine: %d and mailboxNum: %d\n", machID, mailNum);
		(void) interrupt->SetLevel(oldLevel);
		return 0;
	}
	else
	{
		if (busyOrFree)
		{
			printf("[%d] is waiting on lock.\n", machID);
			MachineMailboxID *id = new MachineMailboxID(machID, mailNum);
			waitQueue->Append(id);
			(void) interrupt->SetLevel(oldLevel);
			return 0;
		}
		else
		{
			printf("acquiring lock.\n");
			busyOrFree = 1;
			machineID = machID;
			mailboxNumber = mailNum;
		}
	}
	(void) interrupt->SetLevel(oldLevel);
	return 1;
}

void ServerLock::Release(int machID, int mailNum)
{
	IntStatus oldLevel = interrupt->SetLevel(IntOff);
	if( machineID != machID || mailboxNumber != mailNum)
	{
		printf("Lock Owner mismatched in release.\n");
	}
	else
	{
		if (waitQueue->IsEmpty())
		{	
			printf("No more locks waiting. in release. setting lock status to free.\n");
			DEBUG('n', "The Lock [%s] has been released and is now free.\n" , name);	
			machineID = -1;
			mailboxNumber = -1;
			busyOrFree = 0;
		}
		else
		{
			MachineMailboxID* id = (MachineMailboxID*) waitQueue->Remove();	
			machineID = id->machineID;
			mailboxNumber = id->mailboxNumber;
			printf("Removing machine [%d], mailbox num [%d] from the wait Queue.\n", machineID, mailboxNumber);
			//Construct a packet. 
			PacketHeader pktHdr;
			MailHeader mailHdr;
			pktHdr.to = id->machineID;
			mailHdr.to = id->mailboxNumber;
			mailHdr.from = 0; //from the server.
			mailHdr.length = strlen(serverReply) + 1;	
			//Create the message.
			sprintf(serverReply, "Owner of [%s], is [%d].\n",name, id->machineID);
			//Send the message here. 
			printf("Wakeing up machine[%d] now in ServerLock::Release().\n", pktHdr.to);
			postOffice->Send(pktHdr, mailHdr, serverReply);
		}
	}
	(void) interrupt->SetLevel(oldLevel);
}

int ServerLock::isBusy()
{
	IntStatus oldLevel = interrupt->SetLevel(IntOff);
	if(busyOrFree)
	{
		//BUSY
		(void) interrupt->SetLevel(oldLevel);
		return 1;
	}
	else
	{
		//FREE
		(void) interrupt->SetLevel(oldLevel);
		return 0;
	}
}

ServerCondition::ServerCondition(char* debugName) 
{
	strcpy(name, debugName);
	waitingLock = NULL;
	waitQueue = new List;
}
//Condition variable destructor
ServerCondition::~ServerCondition() 
{
	delete waitingLock;
	delete waitQueue;
	delete name;
}

void ServerCondition::Wait(ServerLock *conditionLock)
{
	IntStatus oldLevel = interrupt->SetLevel(IntOff);
	
	if ( conditionLock == NULL )
	{
		DEBUG('n', "ERROR: In ServerCondition::Wait the lock [%s] passed in is null.\n", name);
		(void) interrupt->SetLevel(oldLevel);
		return;
	}
	if (waitingLock == NULL)
	{
		waitingLock = conditionLock;
	}
	if ( waitingLock != conditionLock)
	{
		DEBUG('n', "ERROR: In ServerCondition::Wait waitingLock does not equal conditionLock [%s]\n", name);
		(void) interrupt->SetLevel(oldLevel);
		return;
	}
	MachineMailboxID *id = new MachineMailboxID(conditionLock->machineID, conditionLock->mailboxNumber);
	printf("Appending machine [%d], mailbox num [%d] to the wait Queue.\n", conditionLock->machineID, conditionLock->mailboxNumber);
	waitQueue->Append(id);
	conditionLock->Release(conditionLock->machineID, conditionLock->mailboxNumber);
	
	(void) interrupt->SetLevel(oldLevel);
	return;
}

int ServerCondition::Signal(ServerLock *conditionLock)
{
	IntStatus oldLevel = interrupt->SetLevel(IntOff);
	
	if ( waitQueue->IsEmpty() )
	{
		DEBUG('n', "ERROR: In ServerCondition::Signal no one waiting on CV [%s]\n", name);
		printf("Wait Queue is empty in ServerCondition::Signal.\n");
		(void) interrupt->SetLevel(oldLevel);
		return 0;
	}
	if (waitingLock != conditionLock)
	{
		DEBUG('n', "ERROR: In Servercondition::Signal waitingLock and conditionLock mismatched: %s\n", name);
		printf("Waiting lock mismatch from conditionLock thats passed in in ServerCondition::Signal.\n");
		(void) interrupt->SetLevel(oldLevel);
		return 0;
	}
	
	MachineMailboxID* id = (MachineMailboxID*) waitQueue->Remove();
	printf("Removing machine [%d], mailbox num [%d] from the wait Queue.\n", id->machineID, id->mailboxNumber);
	PacketHeader pktHdr;
	MailHeader mailHdr;
	char serverReply[100];

	pktHdr.to = id->machineID;
	mailHdr.to = id->mailboxNumber;
	mailHdr.length = strlen(serverReply) + 1;
	mailHdr.from = 0;
	sprintf(serverReply, "%d", 1);
	
	postOffice->Send(pktHdr, mailHdr, serverReply);
	conditionLock->Acquire(id->machineID, id->mailboxNumber);
	
	if (waitQueue->IsEmpty()) //if no ones waiting
	{
		waitingLock = NULL;
	}
	(void) interrupt->SetLevel(oldLevel);
	return 1;
}

void ServerCondition::Broadcast(ServerLock *conditionLock)
{
	IntStatus oldLevel = interrupt->SetLevel(IntOff);
	if (waitingLock != conditionLock)
	{
		DEBUG('n', "ERROR: In ServerCondition::Broadcast waiting Lock and conditionLock mismatched [%s]\n", name);
		(void) interrupt->SetLevel(oldLevel);
		return;
	}
	while(!waitQueue->IsEmpty())
	{
		Signal(conditionLock);
	}
	(void) interrupt->SetLevel(oldLevel);
}

#endif // END NETWORK




